﻿using System;
using System.Collections.Generic;

namespace CSEngine
{
    public delegate Move PlayerTurnHandler(object sender, PlayerTurnArgs e);
    public delegate void BoardUpdatedHandler(object sender, BoardUpdatedArgs e);
    public delegate void GameOverEventHandler(object sender, GameOverArgs e);

    public class Game
    {
        private bool in_progress;
        private Stack<Move> history;
        private GameBoard board;
        private Player player1;
        private Player player2;
        public event BoardUpdatedHandler BoardUpdated;
        public event GameOverEventHandler GameOver;
        private PlayerTurnHandler player1Turn;
        private PlayerTurnHandler player2Turn;

        public Game()
        {
            in_progress = false;
            history = new Stack<Move>(20);
            board = new GameBoard();
            player1 = new Player("Player 1");
            player2 = new Player("Player 2");
            BoardUpdated += DoNothing;  //BoardUpdated event is not required
            GameOver += DoNothing;      //GameOver event is not required
        }

        public Game(byte rows, byte cols)
        {
            in_progress = false;
            history = new Stack<Move>(20);
            board = new GameBoard(rows, cols);
            player1 = new Player("Player 1");
            player2 = new Player("Player 2");
            BoardUpdated += DoNothing;  //BoardUpdated event is not required
            GameOver += DoNothing;      //GameOver event is not required
        }

        private void DoNothing(object sender, EventArgs e)
        {
            return;
        }

        public Game(Player p1, Player p2)
        {
            in_progress = false;
            history = new Stack<Move>(20);
            board = new GameBoard();
            player1 = p1;
            player2 = p2;
        }

        public void BindPlayer1Turn(PlayerTurnHandler handler)
        {
            player1Turn = handler;
        }

        public void BindPlayer2Turn(PlayerTurnHandler handler)
        {
            player2Turn = handler;
        }

        /// <summary>
        /// Get or set the first player (Black) for this game
        /// </summary>
        public Player Player1
        {
            get { return player1; }
            set { player1 = value; }
        }

        /// <summary>
        /// Get or set the second player (White) for this game
        /// </summary>
        public Player Player2
        {
            get { return player2; }
            set { player2 = value; }
        }

        public void UndoLastMove()
        {
            try
            {
                board.MarkSpace(history.Pop(),GameBoard.GameBoardSpace.SpaceColor.EMPTY);
                BoardUpdated.Invoke(this, new BoardUpdatedArgs(board));
            }
            catch (InvalidOperationException)
            { }
        }

        public void Play()
        {
            if (in_progress)
                throw new InvalidOperationException("Game already in progress");

            Move? m = null;
            Move mv;
            in_progress = true;
            board.reset();
            do {
                m = player1Turn.Invoke(this,new PlayerTurnArgs(board,m));
                mv = m.GetValueOrDefault();
                if (board.GetSpace(mv).Color != GameBoard.GameBoardSpace.SpaceColor.EMPTY)
                {
                    // TODO - What should happen if the move is illegal?
                }
                board.MarkSpace(mv,GameBoard.GameBoardSpace.SpaceColor.BLACK);
                history.Push(mv);

                BoardUpdated.Invoke(this,new BoardUpdatedArgs(board));

                if (CheckForWinner())
                    break;

                m = player2Turn.Invoke(this,new PlayerTurnArgs(board,m));
                mv = m.GetValueOrDefault();
                if (board.GetSpace(mv).Color != GameBoard.GameBoardSpace.SpaceColor.EMPTY)
                {
                    // TODO - What should happen if the move is illegal?
                }
                board.MarkSpace(mv, GameBoard.GameBoardSpace.SpaceColor.WHITE);
                history.Push(mv);

                BoardUpdated.Invoke(this, new BoardUpdatedArgs(board));
            } while (!CheckForWinner());

            in_progress = false;
        }

        private bool CheckForWinner()
        {
            byte x, y, count;
            Move lastMove = history.Peek();
            GameBoard.GameBoardSpace.SpaceColor targetColor = board.GetSpace(lastMove).Color;

            // Check for 5-in-a-row in the same row as the last move
            count = 0;
            x = lastMove.x;
            for (y = 1; y <= board.Cols; y++)
            {
                if (board.GetSpace((HexDigit)x, (HexDigit)y).Color == targetColor)
                    count++;
                else
                    count = 0;

                if (count == 5)
                {
                    GameOver.Invoke(this, new GameOverArgs(getPlayerByColor(targetColor)));

                    return true;
                }
            }

            // Check for 5-in-a-row in the same column as the last move
            count = 0;
            y = lastMove.y;
            for (x = 1; x <= board.Rows; x++)
            {
                if (board.GetSpace((HexDigit)x, (HexDigit)y).Color == targetColor)
                    count++;
                else
                    count = 0;

                if (count == 5)
                {
                    GameOver.Invoke(this, new GameOverArgs(getPlayerByColor(targetColor)));

                    return true;
                }
            }

            // Check for 5-in-a-row in the same diagonal as the last move
            count = 0;
            x = lastMove.x;
            y = lastMove.y;
            if (x > y)
            {
                x = (byte)(x - y + 1);
                y = 1;
            }
            else
            {
                y = (byte)(y - x + 1);
                x = 1;
            }
            while (x <= board.Rows && y <= board.Cols)
            {
                if (board.GetSpace((HexDigit)x, (HexDigit)y).Color == targetColor)
                    count++;
                else
                    count = 0;

                if (count == 5)
                {
                    GameOver.Invoke(this, new GameOverArgs(getPlayerByColor(targetColor)));

                    return true;
                }

                x++;
                y++;
            }

            // Check for draw conditions iff there are enough moves in the history
            // that it is possible that there is a draw
            // Currently, a draw is only detected when all spaces are occupied and
            // there is no winner.
            if (history.Count >= board.Rows * board.Cols)
            {
                bool stop = false;
                for (x = 1; x <= board.Rows && !stop; x++)
                {
                    for (y = 1; y <= board.Cols && !stop; y++)
                    {
                        if (board.GetSpace((HexDigit)x, (HexDigit)y).Color == GameBoard.GameBoardSpace.SpaceColor.EMPTY)
                            stop = true;
                    }
                }

                if (!stop)
                {
                    GameOver.Invoke(this, GameOverArgs.CreateDraw());

                    return true;
                }
            }
            
            // If we haven't found anything, then there is not yet a winner
            return false;
        }

        /// <summary>
        /// Retrieve player by color
        /// </summary>
        /// <param name="c">The color of pieces which the player is using</param>
        /// <returns>The player who is using the specified color</returns>
        /// <exception cref="System.ArgumentException">Thrown when c is EMPTY</exception>
        public Player getPlayerByColor(GameBoard.GameBoardSpace.SpaceColor c)
        {
            switch (c)
            {
                case GameBoard.GameBoardSpace.SpaceColor.BLACK:
                    return Player1;
                case GameBoard.GameBoardSpace.SpaceColor.WHITE:
                    return Player2;
                default:
                    throw new ArgumentException();
            }
        }
    }
}
